
/***************************************************************************** \
 *                              ColUtilsOpenSceneGraph
\*****************************************************************************/

/*! @file
 *
 *  @brief
 *    Utils-functions, which help programming with OpenSceneGraph.
 *
 *  @author Weiyu Yi, wyi@tu-clausthal.de
 *
 */

//---------------------------------------------------------------------------
//  Includes
//---------------------------------------------------------------------------

#include <stdio.h>

#include <osg/Math>
#include <osg/Geometry>
#include <osg/PrimitiveSet>

#define COL_EXPORT

#include <col_import_export.h>
#include <ColUtilsOpenSceneGraph.h>

namespace col {

/** the positions of 8 vertices of the standard cube
 */
Point3 Cube::points[8] = { 
		Point3(-1,-1, 1),
		Point3( 1,-1, 1),
		Point3( 1, 1, 1),
		Point3(-1, 1, 1),
		Point3(-1,-1,-1),
		Point3( 1,-1,-1),
		Point3( 1, 1,-1),
		Point3(-1, 1,-1)
};

/** the index array of 6 quads of the standard cube
 */
unsigned int Cube::quat_indices[6*4]={
		0,1,2,3,
		1,5,6,2,
		2,6,7,3,
		0,3,7,4,
		0,4,5,1,
		4,7,6,5
};

/** the index array of 12 triangles of the standard cube
 */
unsigned int Cube::tri_indices[12*3]={
		0,1,2,
		2,3,0,
		1,5,6,
		6,2,1,
		2,6,7,
		7,3,2,
		0,3,7,
		7,4,0,
		0,4,5,
		5,1,0,
		4,7,6,
		6,5,4
};

double Icosahedron::x=0.525731112119133606;
double Icosahedron::z=0.850650808352039932;

/** the positions of 12 vertices of the standard icosahedron
 */
Point3 Icosahedron::points[12] = {
		Point3(	 -x, 0.0,   z ),
		Point3(   x, 0.0,   z ),
		Point3(  -x, 0.0,  -z ),
		Point3(   x, 0.0,  -z ),
		Point3( 0.0,   z,   x ),
		Point3( 0.0,   z,  -x ),
		Point3( 0.0,  -z,   x ),
		Point3( 0.0,  -z,  -x ),
		Point3(   z,   x, 0.0 ),
		Point3(	 -z,   x, 0.0 ),
		Point3(   z,  -x, 0.0 ),
		Point3(  -z,  -x, 0.0 )
};

/** the index array of 20 triangles of a standard icosahedron
 */
unsigned int Icosahedron::indices[20*3] = {
		 1, 4, 0,
		 4, 9, 0,
		 4, 5, 9,
		 8, 5, 4,
		 1, 8, 4,
		 1,10, 8,
		10, 3, 8,
		 8, 3, 5,
		 3, 2, 5,
		 3, 7, 2,
		 3,10, 7,
		10, 6, 7,
		 6,11, 7,
		 6, 0,11,
		 6, 1, 0,
		10, 1, 6,
		11, 0, 9,
		 2,11, 9,
		 5, 2, 9,
		11, 2, 7
};

/** the intermediate helf function called by makeSphere
 *
 * see makeSphere
 */
void addPoint( Point3 pt, unsigned int i, double radius, std::vector<Point3> *points, std::vector<Vector3> *norms )
{
	Vector3 v3( pt[0], pt[1], pt[2] );
	Vector3 norm = normalize( v3 );
	norms->push_back( norm );
	
	Point3 p( norm * radius );
	points->push_back( p );
}

/** the intermediate helf function called by makeSphere
 *
 * see makeSphere
 */
void subdivideTriangle( unsigned int i1,
		   			    unsigned int i2,
						unsigned int i3,
						unsigned int depth,
						double radius,
						unsigned int *len,
						std::vector<Point3> *points, 
						std::vector<Vector3> *norms,
						std::vector<Primitive> *tris )
{
	if ( depth == 0 )
	{
		Primitive tri;
		tri.push_back( i1 );
		tri.push_back( i2 );
		tri.push_back( i3 );
		tris->push_back( tri );

		return;
	}

	Point3 v1 = (*points)[i1];
	Point3 v2 = (*points)[i2];
	Point3 v3 = (*points)[i3];
	Point3 v12, v23, v31;

	v12 = v1 + (v2 - v1) * .5f;
	v23 = v2 + (v3 - v2) * .5f;
	v31 = v3 + (v1 - v3) * .5f;

	unsigned i12 = (*len)++;
	unsigned i23 = (*len)++;
	unsigned i31 = (*len)++;

	addPoint( v12, i12, radius, points, norms );
	addPoint( v23, i23, radius, points, norms );
	addPoint( v31, i31, radius, points, norms );

	subdivideTriangle( i1, i12, i31, depth-1, radius, len, points, norms, tris );
	subdivideTriangle( i2, i23, i12, depth-1, radius, len, points, norms, tris );
	subdivideTriangle( i3, i31, i23, depth-1, radius, len, points, norms, tris );
	subdivideTriangle(i12, i23, i31, depth-1, radius, len, points, norms, tris );
}

/** create a sphere geometry
 *
 * @param osg::Geometry*  pointer to the tobe assigend openscenegraph
 *        geometry
 * @param unsigend int  recursive depth, when 0 the icosahedron will
 *        be created
 * @param double  the radius of sphere
 *
 */
void makeSphere( osg::Geometry * geo, unsigned int depth, double radius )
{
	const double HALF_PI = 1.570796326794897;
	
	// rotate..
	Matrix4 mat = Matrix4::rotation( acos( Icosahedron::z ) + HALF_PI, col::Vector3( 0.0, 1.0, 0.0 ) );

	// initiate the sphere from icosahedron
	std::vector<Point3> points;
	std::vector<Vector3> norms;
	for ( unsigned int i=0; i<12; i++ )
	{
		//Point3 p3( Icosahedron::points[i*3+0], Icosahedron::points[i*3+1], Icosahedron::points[i*3+2] );
		Point3 p3( Icosahedron::points[i] );
		Vector4 v4 = mat * p3;
		Vector3 v3( v4[0], v4[1], v4[2] );

		// Care! normalize( v3 ) =!= normalize( v4 )
		Vector3 norm = normalize( v3 );
		norms.push_back( norm );

		Vector3 vnr = norm * radius;
		Point3 pt( vnr[0], vnr[1], vnr[2] );
		points.push_back( pt );
	}

	std::vector<Primitive> tris;
	// recursive subdivide
	unsigned int len = 12;
	for ( unsigned int i=0; i<20; i++ )
	{
		subdivideTriangle( Icosahedron::indices[ i*3+0 ], Icosahedron::indices[ i*3+1 ], Icosahedron::indices[ i*3+2 ], depth, radius, &len, &points, &norms, &tris );
	}

	// assign to openscenegraph geometry data structure
	osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array;
	for ( unsigned int i=0; i<points.size(); i++ )
	{
		vertices->push_back( osg::Vec3f( points[i][0], points[i][1], points[i][2] ) );
	}
	geo->setVertexArray( vertices.get() );

	for ( unsigned int i=0; i<tris.size(); i++ )
	{
		osg::ref_ptr<osg::DrawElementsUInt> tri = new osg::DrawElementsUInt( osg::PrimitiveSet::TRIANGLES, 0 );
		tri->push_back( tris[i][0] );
		tri->push_back( tris[i][1] );
		tri->push_back( tris[i][2] );
		geo->addPrimitiveSet( tri.get() );
	}

	geo->setName( "Sphere" );
}

/** create a cube geometry
 *
 * @param osg::Geometry*  pointer to the tobe assigned openscenegraph
 *        geometry object
 * @param double  the half length of the edge of the cube
 * @param bool  primitives will be quats if it's set to true,
 *        otherwise triangles
 *
 */
void makeCube( osg::Geometry* geom, double radius, bool quat )
{
	osg::ref_ptr<osg::Vec3Array> v = new osg::Vec3Array();
	v->push_back( osg::Vec3f( -radius, -radius,  radius ) );
	v->push_back( osg::Vec3f(  radius, -radius,  radius ) );
	v->push_back( osg::Vec3f(  radius,  radius,  radius ) );
	v->push_back( osg::Vec3f( -radius,  radius,  radius ) );
	v->push_back( osg::Vec3f( -radius, -radius, -radius ) );
	v->push_back( osg::Vec3f(  radius, -radius, -radius ) );
	v->push_back( osg::Vec3f(  radius,  radius, -radius ) );
	v->push_back( osg::Vec3f( -radius,  radius, -radius ) );
	geom->setVertexArray( v.get() );

	if ( quat == true )
	{
		std::vector< osg::ref_ptr<osg::DrawElementsUInt> > faces( 6 );
		//0,1,2,3,
		//1,5,6,2,
		//2,6,7,3,
		//0,3,7,4,
		//0,4,5,1,
		//4,7,6,5
		faces[0] = new osg::DrawElementsUInt( osg::PrimitiveSet::QUADS, 0 );
		faces[0]->push_back( 0 );
		faces[0]->push_back( 1 );
		faces[0]->push_back( 2 );
		faces[0]->push_back( 3 );
		geom->addPrimitiveSet( faces[0].get() );

		faces[1] = new osg::DrawElementsUInt( osg::PrimitiveSet::QUADS, 0 );
		faces[1]->push_back( 1 );
		faces[1]->push_back( 5 );
		faces[1]->push_back( 6 );
		faces[1]->push_back( 2 );
		geom->addPrimitiveSet( faces[1].get() );

		faces[2] = new osg::DrawElementsUInt( osg::PrimitiveSet::QUADS, 0 );
		faces[2]->push_back( 2 );
		faces[2]->push_back( 6 );
		faces[2]->push_back( 7 );
		faces[2]->push_back( 3 );
		geom->addPrimitiveSet( faces[2].get() );

		faces[3] = new osg::DrawElementsUInt( osg::PrimitiveSet::QUADS, 0 );
		faces[3]->push_back( 0 );
		faces[3]->push_back( 3 );
		faces[3]->push_back( 7 );
		faces[3]->push_back( 4 );
		geom->addPrimitiveSet( faces[3].get() );

		faces[4] = new osg::DrawElementsUInt( osg::PrimitiveSet::QUADS, 0 );
		faces[4]->push_back( 0 );
		faces[4]->push_back( 4 );
		faces[4]->push_back( 5 );
		faces[4]->push_back( 1 );
		geom->addPrimitiveSet( faces[4].get() );

		faces[5] = new osg::DrawElementsUInt( osg::PrimitiveSet::QUADS, 0 );
		faces[5]->push_back( 4 );
		faces[5]->push_back( 7 );
		faces[5]->push_back( 6 );
		faces[5]->push_back( 5 );
		geom->addPrimitiveSet( faces[5].get() );
	}
	else
	{
		std::vector< osg::ref_ptr<osg::DrawElementsUInt> > faces( 12 );
		//0,1,2,
		//2,3,0,
		//1,5,6,
		//6,2,1,
		//2,6,7,
		//7,3,2,
		//0,3,7,
		//7,4,0,
		//0,4,5,
		//5,1,0,
		//4,7,6,
		//6,5,4
		for ( unsigned int i=0; i<12; i++ )
		{
			faces[i] = new osg::DrawElementsUInt( osg::PrimitiveSet::TRIANGLES, 0 );
			faces[i]->push_back( Cube::tri_indices[i*3 + 0] );
			faces[i]->push_back( Cube::tri_indices[i*3 + 1] );
			faces[i]->push_back( Cube::tri_indices[i*3 + 2] );
			geom->addPrimitiveSet( faces[i].get() );
		}
	}


	geom->setName( "Cube" );
}

/**	create a plane geometry
 *
 * @param osg::Geometry*  pointer to the tobe assigend openscenegraph
 *        geometry object
 * @param double  the horizontal length
 * @param double  the vertical length
 * @param unsigned int  the vertical grid number
 * @param unsigend int  the horizontal grid number
 */
void makePlane( osg::Geometry * geo, double xsize, double ysize, unsigned int vert, unsigned int hor )
{
	if ( !vert || !hor )
	{
		fprintf( stderr, "%s : %s\n", __FILE__, __FUNCTION__ );
		fprintf( stderr, "vert and hor must be greater than 0\n" );
		fprintf( stderr, "now vert = %d, hor = %d\n", vert, hor );
		return;
	}

	std::vector<Point3> points;
	std::vector<Primitive> tris;

	double xstep=xsize / hor;
	double ystep=ysize / vert;

	// points
	for ( unsigned int y=0; y <= hor;  y++ )
	for ( unsigned int x=0; x <= vert; x++ )
	{
		points.push_back( Point3( x * xstep - xsize / 2, y * ystep - ysize / 2, 0 ) );
	}
	// primitives
	for ( unsigned int y=0; y < hor;  y++ )
	for ( unsigned int x=0; x < vert; x++ )
	{
		Primitive p1(3);
		p1[0] = x + (y  )*(vert+1);
		p1[1] = x + (y  )*(vert+1) + 1;
		p1[2] = x + (y+1)*(vert+1) + 1;
		tris.push_back( p1 );

		Primitive p2(3);
		p2[0] = x + (y  )*(vert+1);
		p2[1] = x + (y+1)*(vert+1) + 1;
		p2[2] = x + (y+1)*(vert+1);
		tris.push_back( p2 );
	}

	// assign to openscenegraph geometry data structure
	osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array;
	for ( unsigned int i=0; i<points.size(); i++ )
	{
		vertices->push_back( osg::Vec3f( points[i][0], points[i][1], points[i][2] ) );
	}
	geo->setVertexArray( vertices.get() );

	for ( unsigned int i=0; i<tris.size(); i++ )
	{
		osg::ref_ptr<osg::DrawElementsUInt> tri = new osg::DrawElementsUInt( osg::PrimitiveSet::TRIANGLES, 0 );
		tri->push_back( tris[i][0] );
		tri->push_back( tris[i][1] );
		tri->push_back( tris[i][2] );
		geo->addPrimitiveSet( tri.get() );
	}
	geo->setName( "Plane" );
}

}; //namespace col
